/**
 * 
 */
package org.jenkon.time;

import org.jenkon.Resetable;

/**
 * Implementation of a stop watch.
 * 
 * @author Jens Konopka
 * 
 */
public class StopWatch implements Resetable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -7836236801192891638L;

	/**
	 * Set the value to {@link StateType#UNSTARTED}.
	 */
	private static final StateType DEFAULT_TYPE = StateType.UNSTARTED;

	private static final int DEFAULT_TIME = -1;

	private StateType state = DEFAULT_TYPE;

	private long startTime = DEFAULT_TIME;
	private long stopTime = DEFAULT_TIME;

	/**
	 * <p>
	 * Get the running state of the watch.
	 * </p>
	 * 
	 * @return <code>true</code> if the {@link #state} is {@link StateType#RUNNING} else
	 *         <code>false</code>
	 */
	public boolean isRunning() {
		return state == StateType.RUNNING;
	}

	/**
	 * <p>
	 * Start the stop watch.
	 * </p>
	 * 
	 * @throws IllegalStateException If the {@link #state} is {@link StateType#STOPPED} or the
	 *         {@link #state} is not {@link StateType#UNSTARTED}
	 */
	public void start() {

		if (state == StateType.STOPPED) {
			throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
		}

		if (state != StateType.UNSTARTED) {
			throw new IllegalStateException("Stopwatch already started. ");
		}

		stopTime = -1;
		startTime = System.currentTimeMillis();
		state = StateType.RUNNING;

	}

	/**
	 * <p>
	 * Stop the stop watch.
	 * </p>
	 * 
	 * @throws IllegalStateException if the {@link #state} is not {@link StateType#RUNNING} and is
	 *         not {@link StateType#SUSPENDED}.
	 */
	public void stop() {

		if (state != StateType.RUNNING && state != StateType.SUSPENDED) {
			throw new IllegalStateException("Stopwatch is not running. ");
		}

		if (state == StateType.RUNNING) {
			stopTime = System.currentTimeMillis();
		}

		state = StateType.STOPPED;

	}

	/**
	 * <p>
	 * Suspend the stop watch.
	 * </p>
	 * 
	 * @throws IllegalStateException if the {@link #state} is not {@link StateType#RUNNING}.
	 */
	public void suspend() {

		if (state != StateType.RUNNING) {
			throw new IllegalStateException("Stopwatch must be running to suspend. ");
		}

		stopTime = System.currentTimeMillis();
		state = StateType.SUSPENDED;

	}

	/**
	 * <p>
	 * Resume the stop watch.
	 * </p>
	 * 
	 * @throws IllegalStateException if the {@link #state} is not {@link StateType#SUSPENDED}
	 */
	public void resume() {

		if (state != StateType.SUSPENDED) {
			throw new IllegalStateException("Stopwatch must be suspended to resume. ");
		}

		startTime += (System.currentTimeMillis() - stopTime);
		stopTime = DEFAULT_TIME;
		state = StateType.RUNNING;

	}

	/**
	 * <p>
	 * Get the calculated time from start to stop.
	 * </p>
	 * <p>
	 * If the {@link #state} is {@link StateType#UNSTARTED} then return <code>0</code>.
	 * </p>
	 * 
	 * @return the calculated time from start to stop.
	 */
	public long getTime() {
		if (state == StateType.STOPPED || state == StateType.SUSPENDED) {
			return stopTime - startTime;
		} else if (state == StateType.UNSTARTED) {
			return 0;
		}
		return System.currentTimeMillis() - startTime;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jenkon.Resetable#reset()
	 */
	public void reset() {

		state = StateType.UNSTARTED;

		startTime = DEFAULT_TIME;
		stopTime = DEFAULT_TIME;

	}

	/**
	 * <p>
	 * Enum for present the status of {@link StopWatch}.
	 * </p>
	 * 
	 * @author Jens Konopka
	 * 
	 */
	private enum StateType {

		STOPPED, UNSTARTED, RUNNING, SUSPENDED

	}

}
